/*-------------------------------------------------------------------------
 *
 * spell.h
 *
 * ISpell 字典的声明
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 * src/include/tsearch/dicts/spell.h
 *
 *-------------------------------------------------------------------------
 */

#ifndef __SPELL_H__
#define __SPELL_H__

#include "regex/regex.h"
#include "tsearch/dicts/regis.h"
#include "tsearch/ts_public.h"

/*
 * SPNode 和 SPNodeData 用于表示前缀树（Trie），以存储
 * 单词列表。
 */
struct SPNode;

typedef struct
{
	uint32		val:8,
				isword:1,
	/* 存储下面列出的复合标志 */
				compoundflag:4,
	/* 对 AffixData 字段的条目的引用 */
				affix:19;
	struct SPNode *node;
} SPNodeData;

/*
 * FF_ 的名称与 affix 文件中的 Hunspell 选项相关
 * http://hunspell.sourceforge.net/
 */
#define FF_COMPOUNDONLY		0x01
#define FF_COMPOUNDBEGIN	0x02
#define FF_COMPOUNDMIDDLE	0x04
#define FF_COMPOUNDLAST		0x08
#define FF_COMPOUNDFLAG		( FF_COMPOUNDBEGIN | FF_COMPOUNDMIDDLE | \
							FF_COMPOUNDLAST )
#define FF_COMPOUNDFLAGMASK		0x0f

typedef struct SPNode
{
	uint32		length;
	SPNodeData	data[FLEXIBLE_ARRAY_MEMBER];
} SPNode;

#define SPNHDRSZ	(offsetof(SPNode,data))

/*
 * 表示单词列表中的一个条目。
 */
typedef struct spell_struct
{
	union
	{
		/*
		 * 标志由 NIImportDictionary() 填充。 在
		 * NISortDictionary() 之后，d 替代标志使用。
		 */
		char	   *flag;
		/* d 在 mkSPNode() 中使用 */
		struct
		{
			/* 对 AffixData 字段的条目的引用 */
			int			affix;
			/* 单词的长度 */
			int			len;
		}			d;
	}			p;
	char		word[FLEXIBLE_ARRAY_MEMBER];
} SPELL;

#define SPELLHDRSZ	(offsetof(SPELL, word))

/*
 * 如果一个附加符使用正则表达式，我们必须将其单独存储在一个结构中，
 * 该结构在附加符数组扩展或排序时不会移动。
 * 这样可以在上下文销毁时找到并清理。
 */
typedef struct aff_regex_struct
{
	regex_t		regex;
	MemoryContextCallback mcallback;
} aff_regex_struct;

/*
 * 表示附加符列表中的一个条目。
 */
typedef struct aff_struct
{
	char	   *flag;
	/* FF_SUFFIX 或 FF_PREFIX */
	uint32		type:1,
				flagflags:7,
				issimple:1,
				isregis:1,
				replen:14;
	char	   *find;
	char	   *repl;
	union
	{
		aff_regex_struct *pregex;
		Regis		regis;
	}			reg;
} AFFIX;

/*
 * 附加符也使用字典标志
 */
#define FF_COMPOUNDPERMITFLAG	0x10
#define FF_COMPOUNDFORBIDFLAG	0x20
#define FF_CROSSPRODUCT			0x40

/*
 * 不要改变这些的顺序。 初始化根据这些排序，
 * 并期望在排序后前缀首先出现。
 */
#define FF_SUFFIX				1
#define FF_PREFIX				0

/*
 * AffixNode 和 AffixNodeData 用于表示前缀树（Trie），以存储
 * 附加符列表。
 */
struct AffixNode;

typedef struct
{
	uint32		val:8,
				naff:24;
	AFFIX	  **aff;
	struct AffixNode *node;
} AffixNodeData;

typedef struct AffixNode
{
	uint32		isvoid:1,
				length:31;
	AffixNodeData data[FLEXIBLE_ARRAY_MEMBER];
} AffixNode;

#define ANHRDSZ		   (offsetof(AffixNode, data))

typedef struct
{
	char	   *affix;
	int			len;
	bool		issuffix;
} CMPDAffix;

/*
 * Hunspell 字典中附加符标志的编码类型
 */
typedef enum
{
	FM_CHAR,					/* 一个字符（如 ispell） */
	FM_LONG,					/* 两个字符 */
	FM_NUM						/* 数字，>= 0 且 < 65536 */
} FlagMode;

/*
 * 用于存储 Hunspell 选项的结构。 标志表示取决于标志
 * 类型。 这些标志与复合词的支持有关。
 */
typedef struct CompoundAffixFlag
{
	union
	{
		/* 如果 flagMode 是 FM_CHAR 或 FM_LONG，则为标志名称 */
		char	   *s;
		/* 如果 flagMode 是 FM_NUM，则为标志名称 */
		uint32		i;
	}			flag;
	/* 我们没有 bsearch_arg 版本，所以，复制 FlagMode */
	FlagMode	flagMode;
	uint32		value;
} CompoundAffixFlag;

#define FLAGNUM_MAXSIZE		(1 << 16)

typedef struct
{
	int			maffixes;
	int			naffixes;
	AFFIX	   *Affix;

	AffixNode  *Suffix;
	AffixNode  *Prefix;

	SPNode	   *Dictionary;
	/* 后缀集合的数组 */
	char	  **AffixData;
	int			lenAffixData;
	int			nAffixData;
	bool		useFlagAliases;

	CMPDAffix  *CompoundAffix;

	bool		usecompound;
	FlagMode	flagMode;

	/*
	 * 所有以下字段实际上仅在初始化时需要
	 */

	/* 后缀文件中的 Hunspell 选项数组 */
	CompoundAffixFlag *CompoundAffixFlags;
	/* CompoundAffixFlags 数组中的条目数量 */
	int			nCompoundAffixFlag;
	/* CompoundAffixFlags 数组的分配长度 */
	int			mCompoundAffixFlag;

	/*
	 * 剩余字段仅在字典构建期间使用；它们由 NIStartBuild 设置，并由 NIFinishBuild 清除。
	 */
	MemoryContext buildCxt;		/* 构建时的临时上下文 */

	/* 字典文件中所有单词的临时数组 */
	SPELL	  **Spell;
	int			nspell;			/* Spell 数组中的有效条目数量 */
	int			mspell;			/* Spell 数组的分配长度 */

	/* 这些用于分配没有 palloc 开销的“紧凑”数据 */
	char	   *firstfree;		/* 第一个空闲地址（始终最大对齐） */
	size_t		avail;			/* 在 firstfree 处剩余的空闲空间 */
} IspellDict;

extern TSLexeme *NINormalizeWord(IspellDict *Conf, char *word);

extern void NIStartBuild(IspellDict *Conf);
extern void NIImportAffixes(IspellDict *Conf, const char *filename);
extern void NIImportDictionary(IspellDict *Conf, const char *filename);
extern void NISortDictionary(IspellDict *Conf);
extern void NISortAffixes(IspellDict *Conf);
extern void NIFinishBuild(IspellDict *Conf);

#endif
